package org.pizza.dlock.distributed.lock.generator;

import org.pizza.dlock.distributed.lock.api.DLock;
import org.pizza.dlock.distributed.lock.api.LockField;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author 高巍
 * @since  2019-11-27 11:40 上午
 */
public class DefaultLockKeyGenerator implements LockKeyGenerator {
    private static final Logger logger = LoggerFactory.getLogger(DefaultLockKeyGenerator.class);

    private final List<Class<?>> support = Arrays.asList(String.class, Long.TYPE, Integer.TYPE, Byte.TYPE, Character.TYPE, Double.TYPE, Float.TYPE);


    @Override
    public String generateKey(MethodInvocation methodInvocation) {
        //jdk动态代理的话这里就是个接口方法
        Method method = methodInvocation.getMethod();
        //这里需要获取指定类的方法
        Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, methodInvocation.getThis().getClass());
        //获取方法注解
        DLock rLock = mostSpecificMethod.getAnnotation(DLock.class);
        //根据获取方法参数 和 值
        Parameter[] parameters = mostSpecificMethod.getParameters();
        Object[] arguments = methodInvocation.getArguments();

        StringBuilder builder = new StringBuilder();
        //1解析方法参数是否含有锁字段
        for (int i = 0; i < parameters.length; i++) {
            final LockField lockField = parameters[i].getAnnotation(LockField.class);
            if (lockField == null) {
                continue;
            }
            Object object = arguments[i];
            Class<?> type = parameters[i].getType();
            //如果是非自定义类则直接使用值
            if (support.contains(type)) {
                builder.append(rLock.delimiter()).append(object);
            }
            //是否指向对象中的字段
            else if (!StringUtils.isEmpty(lockField.value())) {
                List<Field> fields = this.allFields(object.getClass());
                //遍历获取字段的值拼接为锁的Key
                for (Field field : fields) {
                    if (lockField.value().equals(field.getName())) {
                        field.setAccessible(true);
                        builder.append(rLock.delimiter()).append(ReflectionUtils.getField(field, object));
                    }
                }
            } else {
                logger.warn("@LockField 注解的方法参数，非直接支持的类型，并且name没有指定上锁的字段");
            }
        }
        //2遍历对象中的属性是否有@LockField
        if (StringUtils.isEmpty(builder.toString())) {
            final Annotation[][] parameterAnnotations = mostSpecificMethod.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                final Object object = arguments[i];
                if(object == null){
                    continue;
                }
                List<Field> fields = this.allFields(object.getClass());
                for (Field field : fields) {
                    final LockField lockField = field.getAnnotation(LockField.class);
                    if (lockField == null) {
                        continue;
                    }
                    field.setAccessible(true);
                    builder.append(rLock.delimiter()).append(ReflectionUtils.getField(field, object));
                }
            }
        }
        if (!StringUtils.isEmpty(builder.toString())) {
            return rLock.prefix() + builder;
        }
        //默认使用方法名
        return this.generateKeyForMethod(mostSpecificMethod);
    }

    private String generateKeyForMethod(Method mostSpecificMethod) {
        DLock dLock = mostSpecificMethod.getAnnotation(DLock.class);
        return dLock.prefix() + dLock.delimiter() + mostSpecificMethod.getName();
    }

    private List<Field> allFields(Class<?> c) {
        List<Field> fieldList = new ArrayList<>();
        Class<?> tempClass = c;
        //当父类为null的时候说明到达了最上层的父类(Object类).
        while (tempClass != null) {
            fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            //得到父类,然后赋给自己
            tempClass = tempClass.getSuperclass();
        }
        return  fieldList;
    }
}
